package com.shangyueshang.utils;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.util.FastByteArrayOutputStream;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;
import java.util.Objects;


@Slf4j
public abstract class ExcelUtil {

    public static final Integer EXPORT_LIMIT = 5000;
    /**
     * @see <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types">Common MIME types</a>
     */
    @SuppressWarnings("SpellCheckingInspection")
    private static final MediaType MICROSOFT_EXCEL_XLSX
            = MediaType.valueOf("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    /**
     * @see <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types">Common MIME types</a>
     */
    private static final MediaType MICROSOFT_EXCEL_XLS = MediaType.valueOf("application/vnd.ms-excel");

    public static OutputStream getOutputStream(HttpServletResponse response) throws IOException {
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, generateContentDisposition());
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setHeader(HttpHeaders.PRAGMA, "No-cache");
        response.setHeader(HttpHeaders.CACHE_CONTROL, "no-store");
        response.setDateHeader(HttpHeaders.EXPIRES, 0);
        return response.getOutputStream();
    }

    public static MediaType getMicrosoftExcelXlsx() {
        return MICROSOFT_EXCEL_XLSX;
    }

    @SneakyThrows
    public static String generateContentDisposition() {
        return "attachment; filename=" + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now()) + ".xlsx";
    }

    public static ResponseEntity<Resource> getResponseEntity(@Nullable Resource resource) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.set(
                HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
                HttpHeaders.CONTENT_DISPOSITION
        );
        httpHeaders.set(HttpHeaders.CONTENT_DISPOSITION, generateContentDisposition());
        httpHeaders.setCacheControl(CacheControl.noCache());
        httpHeaders.setContentType(MICROSOFT_EXCEL_XLSX);

        if (Objects.nonNull(resource) && resource instanceof ByteArrayResource) {
            // 如果是 ByteArrayResource 就设置内容长度
            // 否则如果是 org.springframework.core.io.InputStreamResource 没有重新 contentLength() 方法
            // 就会调用 getInputStream() 读取长度 因为 InputStreamResource 只支持读取一次 在这里读取之后 mvc 在读取就会报错
            httpHeaders.setContentLength(((ByteArrayResource) resource).contentLength());
        }

        return ResponseEntity.ok()
                .headers(httpHeaders)
                .body(resource);
    }

    /**
     * excel导出 (导出列数不包括前端传入excludeColumns集合参数)
     *
     * @param response
     * @param excludeColumns
     * @param dataList
     * @param clazz
     * @param <T>
     */
    public static <T> void excelSimpleExcludeWrite(HttpServletResponse response, Collection<String> excludeColumns, List<T> dataList, Class<T> clazz) throws IOException {
        // 设置头部及内容样式
        HorizontalCellStyleStrategy horizontalCellStyleStrategy = initStyleStrategy();
        synchronized (clazz) {
            ExcelWriterBuilder writer = EasyExcelFactory.write(getOutputStream(response), clazz);
            writer.registerWriteHandler(horizontalCellStyleStrategy)
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .excludeColumnFiledNames(excludeColumns)
                    .sheet("Sheet1")
                    .doWrite(dataList);
        }
    }

    /**
     * excel导出 (出列数只包括前端传入includeColumns集合参数)
     *
     * @param response
     * @param includeColumns
     * @param dataList
     * @param clazz
     * @param <T>
     */
    public static <T> void excelSimpleIncludeWrite(HttpServletResponse response, Collection<String> includeColumns, List<T> dataList, Class<T> clazz) throws IOException {
        // 设置头部及内容样式
        HorizontalCellStyleStrategy horizontalCellStyleStrategy = initStyleStrategy();
        synchronized (clazz) {
            ExcelWriterBuilder writer = EasyExcelFactory.write(getOutputStream(response), clazz);
            writer.registerWriteHandler(horizontalCellStyleStrategy)
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .includeColumnFiledNames(includeColumns)
                    .sheet("Sheet1")
                    .doWrite(dataList);
        }
    }

    /**
     * excel导出 (出列数只包括前端传入includeColumns集合参数)
     * 获得 spring mvc 可解析的响应文件流
     */
    public static <T> ResponseEntity<Resource> getExcelSimpleResponseEntity(
            Collection<String> includeColumns,
            List<T> dataList,
            Class<T> clazz,
            WriteHandler... writeHandlers
    ) {
        return getResponseEntity(getExcelSimpleResource(includeColumns, dataList, clazz, writeHandlers));
    }

    public static <T> ByteArrayInputStream getExcelSimpleInputStream(
            Collection<String> includeColumns,
            List<T> dataList,
            Class<T> clazz,
            WriteHandler... writeHandlers
    ) {
        return new ByteArrayInputStream(
                getExcelSimpleOutputStream(includeColumns, dataList, clazz, writeHandlers)
                        .toByteArrayUnsafe()
        );
    }

    public static <T> ByteArrayResource getExcelSimpleResource(
            Collection<String> includeColumns,
            List<T> dataList,
            Class<T> clazz,
            WriteHandler... writeHandlers
    ) {
        return new ByteArrayResource(
                getExcelSimpleOutputStream(includeColumns, dataList, clazz, writeHandlers)
                        .toByteArrayUnsafe()
        );
    }

    public static <T> FastByteArrayOutputStream getExcelSimpleOutputStream(
            Collection<String> includeColumns,
            List<T> dataList,
            Class<T> clazz,
            WriteHandler... writeHandlers
    ) {
        synchronized (clazz) {
            FastByteArrayOutputStream fbaos = new FastByteArrayOutputStream();
            ExcelWriterBuilder excelWriterBuilder = createExcelWriterBuilder(fbaos, writeHandlers).head(clazz);
            excelWriterBuilder
                    .includeColumnFiledNames(includeColumns)
                    .sheet("Sheet1")
                    .doWrite(dataList);
            return fbaos;
        }
    }

    public static ExcelWriterBuilder createExcelWriterBuilder(OutputStream os, WriteHandler... writeHandlers) {
        // 设置头部及内容样式
        ExcelWriterBuilder excelWriterBuilder = EasyExcelFactory.write(os)
                .registerWriteHandler(initStyleStrategy())
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());

        if (Objects.nonNull(writeHandlers)) {
            for (WriteHandler writeHandler : writeHandlers) {
                excelWriterBuilder.registerWriteHandler(writeHandler);
            }
        }
        return excelWriterBuilder;
    }

    /**
     * 设置头部及内容样式策略
     */
    public static HorizontalCellStyleStrategy initStyleStrategy() {
        WriteCellStyle contentCellStyle = new WriteCellStyle();
        contentCellStyle.setWrapped(true);
        contentCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        return new HorizontalCellStyleStrategy(new WriteCellStyle(), contentCellStyle);
    }


}
